/*
*    UNIVERSIDADE DE SÃO PAULO.
*    Author: Marco Aurélio Gerosa (gerosa@ime.usp.br)
*    This project was/is sponsored by RNP and FAPESP.
*
*    This file is part of Groupware Workbench (http://www.groupwareworkbench.org.br).
*
*    Groupware Workbench is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Lesser General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    Groupware Workbench is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU Lesser General Public License for more details.
*
*    You should have received a copy of the GNU Lesser General Public License
*    along with Swift.  If not, see <http://www.gnu.org/licenses/>.
*/
package br.org.groupwareworkbench.collablet.coop.recommend;

import br.org.groupwareworkbench.core.xmlconfig.XmlSpecificConfig;
import br.org.groupwareworkbench.core.bd.AssignedTo;
import br.org.groupwareworkbench.core.bd.EntityManagerProvider;
import br.org.groupwareworkbench.core.bd.GenericReference;
import br.org.groupwareworkbench.core.bd.ObjectDAO;
import br.org.groupwareworkbench.core.framework.Collablet;
import br.org.groupwareworkbench.core.util.EntityMapper;

import com.collectialf.textanalyzer.TermVector;
import com.collectialf.textanalyzer.TextAnalyzer;
import com.collectialf.textanalyzer.dotproduct.SimpleFrequency;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.TypedQuery;

@Entity
@Table(name="gw_collab_Recommend")
public class Recommend implements Serializable {

    private static final long serialVersionUID = -3399306816323250833L;

    private static final ObjectDAO<Recommend, Long> DAO = new ObjectDAO<Recommend, Long>(Recommend.class);

    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne
    private Collablet collablet;

    /*
     * AttributeOverrides é para que diferenciar os campos das duas classes
     * GenericReference, para que o JPA possa diferenciá-las.
     */

    @Embedded
    @AssignedTo
    @AttributeOverrides({
        @AttributeOverride(name="pk", column=@Column(name="fromPk")),
        @AttributeOverride(name="className", column=@Column(name="fromClassName"))
    })
    private GenericReference sourceReference;

    @Embedded
    @AssignedTo
    @AttributeOverrides({
        @AttributeOverride(name="pk", column=@Column(name="toPk")),
        @AttributeOverride(name="className", column=@Column(name="toClassName"))
    })
    private GenericReference destinationReference;

    private double similarity;

    // Construtores.

    protected Recommend() {
    }

    public Recommend(XmlSpecificConfig xmlConfig, Object source, Object destination) {

        GenericReference from = new GenericReference(source);
        GenericReference to = new GenericReference(destination);

        this.sourceReference = from;
        this.destinationReference = to;
        this.similarity = calculateSimilarity(xmlConfig);
    }

    // Id.

    public Long getId() {
        return id;
    }

    public Collablet getCollablet() {
        return collablet;
    }

    public void setCollablet(Collablet collablet) {
        this.collablet = collablet;
    }

    // Recomendador.

    public void setSource(Object source) {
        this.sourceReference = new GenericReference(source);
    }

    public Object getSource() {
        return sourceReference.getEntity();
    }

    // Usado em widgets.
    public Serializable getSourcePk() {
        return sourceReference.getPk();
    }

    // Recomendado.

    public void setDestination(Object destination) {
        this.destinationReference = new GenericReference(destination);
    }

    public Object getDestination() {
        return destinationReference.getEntity();
    }

    // Usado em widgets.
    public Serializable getDestinationPk() {
        return destinationReference.getPk();
    }

    // Similaridade.

    public double getSimilarity() {
        return similarity;
    }

    private static final Set<String> STOP_WORDS = Collections.<String>emptySet();
    private static final Set<String> NO_STEMMING = Collections.<String>emptySet();
    private static final Map<String, String> SINONYMS = Collections.<String, String>emptyMap();

    private static TermVector getTagVector(XmlSpecificConfig xmlConfig, Object entity) {
        try {
            StringBuilder text = new StringBuilder(16 * 1024);
            List<String> texts = xmlConfig.extractMethods(entity);
            for (String string : texts) {
                text.append(string);
            }
            return new TextAnalyzer(STOP_WORDS, NO_STEMMING, SINONYMS).analyze(text.toString());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private double calculateSimilarity(XmlSpecificConfig xmlConfig) {
/*        TermVector termsFrom = getTagVector(xmlConfig, sourceReference.getEntity());
        TermVector termsTo = getTagVector(xmlConfig, destinationReference.getEntity());
        return new SimpleFrequency().cross(termsFrom, termsTo);
        */
        TermVector termsFrom = getTagVector(xmlConfig, sourceReference.getEntity());
        TermVector termsTo = getTagVector(xmlConfig, destinationReference.getEntity());
        double similarity = new SimpleFrequency().cross(termsFrom, termsTo);
        int similarityInt = (int)(similarity *10000);
        return similarityInt/1000.0;
        
    }

    // Outros

    public Recommend getSwapped() {
        Recommend swapped = new Recommend();
        swapped.destinationReference = this.sourceReference;
        swapped.sourceReference = this.destinationReference;
        swapped.similarity = this.similarity;
        return swapped;
    }

    public void save() {
        DAO.save(this);
    }

    public void delete() {
        DAO.delete(this);
    }

    public static List<Recommend> listRelated(Object entity, int numRelated, Collablet collablet) {
        EntityManager em = EntityManagerProvider.getEntityManager();

        String queryString =
                "SELECT a FROM " + Recommend.class.getSimpleName() + " a" +
                " WHERE a.sourceReference = :entity OR a.destinationReference = :entity" +
                " AND a.collablet = :collablet" +
                " ORDER BY a.similarity DESC";

        TypedQuery<Recommend> query = em.createQuery(queryString, Recommend.class);
        query.setFirstResult(0);
        query.setMaxResults(numRelated);
        query.setParameter("entity", new GenericReference(entity));
        query.setParameter("collablet", collablet);

        List<Recommend> relatedList = query.getResultList();
        List<Recommend> related = new ArrayList<Recommend>(relatedList.size());

        for (Recommend recommend : relatedList) {
            related.add(EntityMapper.getDefaultInstance().equalsPk(entity, recommend.getDestination()) ? recommend.getSwapped() : recommend);
        }
        return related;
    }
}